package com.life.ai.server.framework.redis.aspect;

import com.life.ai.server.common.utils.RedisUtils;
import com.life.ai.server.framework.redis.annotation.RedisCache;
import com.life.ai.server.framework.redis.annotation.RedisEvict;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;

/**
 * RedisLockAspect
 * redis分布式锁 切面
 * <p>
 * PS: 事务切面的执行优先级不能小于分布式锁,否则会导致分布式锁失效!
 *
 * @author kylewka
 * @since 2023/6/25
 */
@Slf4j
@Aspect
@Component
@Order(0)//确保比事务注解先执行，分布式锁在事务外
public class RedisCacheAspect {

    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    /**
     * 从缓存中获取,不存在时从函数实现内部获取并添加到缓存
     *
     * @param joinPoint  方法切面,用户获取参数
     * @param redisCache 方法锁注解配置
     * @return 目标方法的返回内容
     */
    @Around(value = "@annotation(redisCache)")
    public Object redisCache(ProceedingJoinPoint joinPoint, RedisCache redisCache) throws Throwable {
        // 拼接出key
        String key = getRedisKeyBySpel(joinPoint, redisCache.name(), redisCache.keys());
        if (RedisUtils.hasKey(key)) {
            return RedisUtils.get(key);
        }
        // 缓存key不存在
        Object obj = joinPoint.proceed();
        boolean cacheNull = obj == null && redisCache.cacheNull();
        if (cacheNull) {
            // 缓存空值
            RedisUtils.set(key, null, redisCache.nullTtl());
        } else {
            RedisUtils.set(key, obj, redisCache.ttl());
        }
        return obj;
    }


    /**
     * 分布式锁切面方法
     *
     * @param joinPoint  方法切面,用户获取参数
     * @param redisEvict 方法锁注解配置
     * @return 目标方法的返回内容
     */
    @Around(value = "@annotation(redisEvict)")
    public Object redisEvict(ProceedingJoinPoint joinPoint, RedisEvict redisEvict) throws Throwable {
        // 执行目标方法
        Object result = joinPoint.proceed();
        // 删除指定缓存
        RedisUtils.del(getRedisKeyBySpel(joinPoint, redisEvict.name(), redisEvict.keys()));
        return result;
    }

    /**
     * 通过切面函数获取el表达式
     *
     * @param joinPoint 方法切面
     * @param prefix    字符前缀
     * @param keys      keys
     * @return redisKey
     */
    public static String getRedisKeyBySpel(ProceedingJoinPoint joinPoint, String prefix, String[] keys) {
        EvaluationContext context = new MethodBasedEvaluationContext(TypedValue.NULL, resolveMethod(joinPoint), joinPoint.getArgs(), parameterNameDiscoverer);
        StringBuilder sb = new StringBuilder().append(prefix);
        ExpressionParser parser = new SpelExpressionParser();
        for (String key : keys) {
            // keys是个spel表达式
            Expression expression = parser.parseExpression(key);
            Object value = expression.getValue(context);
            sb.append(ObjectUtils.nullSafeToString(value));
        }
        return sb.toString();
    }

    private static Method resolveMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();

        return getDeclaredMethodFor(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
    }

    private static Method getDeclaredMethodFor(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethodFor(superClass, name, parameterTypes);
            }
        }
        throw new IllegalStateException("Cannot resolve target method: " + name);
    }

}
